Coding Standards for UI Code Development

In the field of UI code development, these principles become all the more critical as user interfaces directly interact with end-users. The following comprehensive coding standards and practices outline a blueprint for creating top-tier UI components. From standardized naming conventions to advanced testing strategies, these guidelines contribute to a codebase that is not only functional but also scalable, adaptable, and responsive. Follow these coding standards to ensure consistency, quality, and maintainability throughout the development process. These standards offer clear guidelines and best practices for UI code developers:

  • Naming Conventions and Folder Structure

    When developing new components like configuration or data widgets, you must adhere to consistent naming conventions and folder structures. Follow these guidelines:

    • The file name should match the component name. For example, if the widget is named "SampleAdaptor," the file name should be "SampleAdaptor.js."

    • For utility methods within the component, name them in all lowercase with hyphens to separate words, e.g., "adaptor-util.js."

    • Maintain a structured folder hierarchy such as:

      Copy
      /sample-adaptor
      index.js
      SampleAdaptor.js
      adaptor-util.js
    •  

  • Functional Components

    Use functional components over class-based components. Functional components are more concise, easier to understand, and encourage a declarative coding style. Additionally, they promote the use of React hooks, enabling better state management, reusability, and composability. This shift aligns with the latest trends in React development, enhancing both the development process and the final product.

  • Minimal and Well-Defined Props

    Keep React component props to a minimum and ensure they have well-defined types. For example:

    Copy
    TestComponent({projectId, releaseId, workstreamId})

    This interface exposes unique identifiers for project, release, and workstream. Relevant details about these entities can be retrieved by using these identifiers.

  • Limited Use of Redux

    Use Redux sparingly, only when absolutely necessary. For example, you can use Redux for managing data that requires broad access across the platform, such as for managing the currently logged-in user or tenant ID. When a user logs in, their ID or tenant ID can be stored in the Redux store. This facilitates using these values consistently throughout the platform, such as displaying user names or including tenant ID in REST API calls.

  • API Calls and React-Query

    To ensure efficient handling of API calls, use the react-query library. React-query simplifies asynchronous state management in React applications. It abstracts away the complexities of dealing with API responses by providing easy-to-use variables that detect success or errors, as well as organized data fetching. This approach eliminates the need to manually check for success or error codes and wrap code in unnecessary try/catch blocks. By employing react-query, you enhance code readability and reduce potential errors associated with manual error handling.

  • Consistent Styling with React Bootstrap

    Maintain uniformity and consistency in the appearance of components by building them using the React Bootstrap design system. Following design specifications ensures that components are styled according to the established visual guidelines. Additionally, it's recommended that all components adhere to the bootstrap fluid model. This practice guarantees that components render correctly and maintain their intended appearance across various environments, regardless of screen size or device.

  • Modular Approach for Complex Functions

    Adopt a modular approach when dealing with complex functions. Rather than composing extensive logic within a single function, try to break down intricate tasks into simpler functions. Each function should ideally focus on performing a single task or responsibility. This not only enhances code readability but also promotes reusability.

    For example, consider a situation where user data validation involves intricate rules for both email and password validation. Instead of cramming all the validation logic into a single function [for example, validateUserComplex()], it's beneficial to decompose the email and password validation into smaller, reusable functions. This approach would enable you to create separate functions like validateEmail(email) and validatePassword(password).

    Here is how you can break down complex logic into simple functions:

    Copy
    const validateUserComplex = (user) => {
    // validate user email
    //validate user password
    // based on the above validations return success or error
    }
    const validateUserSimple = (user) => {
    //function call to validateEmail(user.email)
    //function call to validatePassword
    //based on above two responses return success or error
    }
    const validateEmail = (email) => {
    //logic to validate email and return success or error
    }
    const validatePassword = (password) => {
    //logic to validtae password and return success or error
    }
  • Custom Query Hooks and Caching

    To optimize API calls and enhance performance, develop custom query hooks for making API requests. Cache the API responses using react-query wherever possible. For example, when fetching configurations for a specific provider code, create a custom hook like useGetConfigurations(providerCode) and ensure that the query results are cached. This approach improves data retrieval efficiency and minimizes redundant API calls.

  • Prop Pass-through and Query Hooks

    Rather than passing API query data as props to child components, leverage custom query hooks for data retrieval. As data fetched using query hooks is cached, these hooks offer an efficient way to retrieve data. Component props should primarily consist of unique identifiers such as projectId or workstreamId, which are then utilized to identify and fetch corresponding data.

  • Separation of Third-Party Data

    When an API response contains a mix of application-level and third-party data, consider segregating the third-party data into a separate API. By doing so, load time of the main API response is not impacted if the third-party service experiences downtime. This separation ensures the main data remains accessible even when third-party data retrieval is disrupted.

  • URL-Based Data Handling

    Data needed for a specific route should be included in the URL either as path parameters or query parameters. It's advisable to avoid passing data as component state in React. Instead, derive component-specific data from the unique identifiers present in the URL's path or query parameters.

  • Styling Best Practices

    Inline styling should be minimized. Whenever possible, utilize common classes available in the platform's styling framework to ensure consistency across the user interface. If no appropriate classes exist, create new class names and define styles in separate CSS/SCSS files to maintain consistent styling practices.

  • Leveraging Lodash

    When required, make use of lodash library methods instead of reinventing utility functions. Lodash provides a plethora of utility functions that can simplify complex operations, enhance code readability, and reduce potential errors.

  • Efficient Lodash Import

    Prefer named imports while utilizing lodash library methods. Named imports allow you to selectively load only the required modules, optimizing the output bundle size. This approach ensures efficient usage of lodash's utility functions.

  • Performance Optimization with Hooks

    Utilize useCallback and useMemo as applicable to optimize performance. useCallback ensures that functions are recreated only when their dependencies change, while useMemo returns memoized values, enhancing performance by reducing unnecessary computations.

  • Exhaustive Dependencies

    Ensure that useEffect and useCallback have exhaustive dependencies listed. Each dependency relevant to these hooks should be included in the dependencies array to ensure proper timing and execution of effects and memoized functions.

  • Destructuring Props

    Destructuring props inside components is a recommended technique. By doing so, the code becomes more readable, repetition is reduced, and renaming becomes easier. Destructuring also promotes better organization and maintainability of your React components.

  • Enforce React Hook Rules

    Enable the "react-hooks/rules-of-hooks": "error" ESLint rule. This configuration enforces adherence to the rules of hooks, ensuring correct usage and preventing potential issues related to hook usage.

  • Yup for Form Validation

    Leverage the Yup library for form validation within components. Yup simplifies the process of defining and managing validation rules, centralizing validation logic, and separating it from the rendering logic.

  • String Translation and Localization

    Ensure that all static strings are translatable. Even strings with variables should be wrapped using translation macros like Trans or t. This practice guarantees proper content display in various languages while maintaining formatting and context.

  • Code Cleanup

    Before pushing code, remove all unused variables, imports, commented-out code, console logs, and debuggers. Eliminating unused artifacts ensures that the codebase remains clean, uncluttered, and free from potential sources of errors.

  • useHistory for Navigation

    Use the useHistory hook for navigation purposes. This hook grants access to the history object, enabling navigation via methods like push and goBack. Ensure that useHistory is utilized within components rendered within a Router context.

  • Skeleton Loading Patterns

    Avoid blocking the entire page with a loader. Instead, consider using section-wise skeleton loading patterns. These patterns enhance visual appeal and provide users with a clearer understanding of upcoming content, contributing to a perceived improvement in application performance.

Error Handling

Effective error handling is crucial for maintaining a seamless user experience and ensuring robust application behavior. Follow these guidelines to ensure a consistent and user-friendly approach as you develop adapters for the Calibo Accelerate platform:

  • Error Handling Strategies

    • User Action Errors: Errors occurring due to user actions, such as input validation failures, should be communicated by using snackbars or inline error messages. These provide immediate feedback to users and help them rectify their actions.

    • API Response Errors: API response errors should also be conveyed through inline error messages. This practice maintains coherence in error communication, enhancing the user experience.

    • System/ Network Errors: Errors arising from system or network issues, such as internet connectivity problems, should trigger snackbars. This approach ensures that users are informed of external factors affecting their interaction with the platform.

  • Error Interface and Structure

    The error interface looks similar to the following:

    • getErrorMessageNew Function (error, customMessage): A common function, getErrorMessageNew(error, customMessage), helps in deriving meaningful error messages from error responses.

    • Error Structure: The error object follows the following structured format:

      • code: A standardized code for the error message.

        It combines <ServiceInitials>-<Type>-<Numeric Code>, such as MA-E-1001 for Maturity Assessment service, error type, and numeric code.

      • message: A string message representing the error itself.

      • subMessages: An optional array of objects for representing sub-messages. This accommodates scenarios where multiple error messages are returned, such as during CSV imports.

  • Using useSnackbar Hook

    Use the useSnackbar hook to show success and error messages to users. Here's how you can effectively utilize this common hook:

    • Deriving 'addSnackbar' from useSnackbar

      Derive addSnackbar from useSnackbar. This function is useful in adding messages to the snackbar notification system.

      Copy
      const { addSnackbar } = useSnackbar();
    • Applying addSnackbar to Display Error Messages

      You can use the 'addSnackbar' function to display error messages resulting from various scenarios. Here's how it can be used in conjunction with the getErrorMessageNew function, which is a commonly used method to format and retrieve error messages from API responses:

      Copy
      // Assuming 'err' is the error object from an API response
                          addSnackbar(getErrorMessageNew(err), 'error');
  • Assign Default Values and Avoid Page Breaks

    When accessing the inner details of props or objects, assign default values to avoid potential page break issues. This ensures a seamless user experience, preventing unexpected behavior due to undefined or missing values.

  • Limited Use of try-catch Blocks:

    Use try-catch blocks sparingly to handle errors in your code. Place only the code that is likely to throw an error inside the try block. The catch block then handles the error. This prevents excessive error suppression and promotes efficient debugging.

    Copy
    try {
    // Code that may throw an error
    } catch (error) {
    // Code to handle the error
    }

By adhering to these error handling standards, you can ensure a robust and user-friendly adapter with smooth user experiences and effective troubleshooting.

Testing Requirements

To ensure the robustness and reliability of your UI code, adhering to comprehensive testing practices is imperative. Here's how you can effectively handle testing requirements for UI code development:

  • Component Level Testing with Jest

    Component-level tests play an important role in verifying the correctness of your UI components. Write these tests using the Jest framework. Organize your tests within the component itself in a dedicated "test" folder:

    Copy
    /Component
    /tests
    ComponentTest.test.js
  • Focused Component Functionality Testing

    When writing component tests, focus solely on testing the functionality of the component itself. Mock the inputs and properties that the component depends on to isolate the component's behavior.

  • Single Component per Test File

    Maintain clarity and separation by including only one component, class, or module within each test file. This keeps your tests well-structured and easily maintainable.

  • Organize Tests with Describe and Test Blocks

    Arrange your test steps within descriptive describe and test/it blocks. This enhances readability and helps to clearly define the scope of each test:

    Copy
    describe('Button', () => {
       it('Should render a button with the class of primary', () => { ... })
    })
  • Utilize Hooks for Common Code and Cleanup

    Leverage Jest's hooks to streamline common setup and cleanup tasks. Use 'beforeEach', 'beforeAll', 'afterEach', and 'afterAll' hooks to ensure consistency across your tests and reset any mocks:

    Copy
    describe('Before and after hooks', () => {
       beforeAll(() => setupOnce())
       beforeEach(() => setupEach())
       afterAll(() => resetOnce())
       afterEach(() => resetEach())
    })
  • Test Independence

    Ensure the independence of your tests. Each test should function without relying on the outcome of other tests. This approach improves test reliability and makes troubleshooting easier.

  • Identify End-to-End Flows with Cypress

    Identify major end-to-end user flows within your application and write tests for these scenarios by using Cypress. Configure these tests to run automatically on Jenkins, ideally on a daily basis. This regular testing helps catch any regressions and maintain the overall stability of your UI codebase.

By following these comprehensive testing guidelines, you can elevate the quality of your UI code, minimize potential issues, and foster confidence in the functionality of your user interface.

Performance, Scalability and Reliability Requirements (PSR)

Performance, Scalability, and Reliability (PSR) are essential aspects of UI code development that directly impact the user experience and the overall quality of the application. Adhering to PSR requirements ensures that the application performs efficiently, scales effectively, and maintains a high level of reliability. Here are a few key practices to follow:

  • Optimal Use of React Hooks

    Make judicious use of React hooks, avoiding unnecessary ones that could impact performance. Using hooks like 'useState', 'useEffect', and 'useCallback' is important, but excessive or improper usage can lead to unnecessary re-renders and reduced performance.

  • Dynamic Imports for Widgets

    For performance optimization, utilize dynamic imports when integrating configuration or data widgets into the Calibo Accelerate platform. Dynamic imports allow these components to be loaded only when needed, minimizing the initial loading time and improving the application's responsiveness.

  • Efficient API Responses

    Ensure that API responses remain lightweight, especially when fetching data for UI widgets. If an API response contains a mix of data, such as application-level and third-party data, consider separating them into separate API calls. This segregation reduces the application-level data load time, enhancing performance.

  • Memoization of React Components

    Implement memoization for React components whenever appropriate. Memoization prevents unnecessary re-renders by caching the output of a component, improving performance, particularly for large or frequently updated components.

  • Optimized List Rendering

    When dealing with lists in React, assign unique key attributes to list elements. Properly assigning keys helps React efficiently render and update list items, preventing unnecessary suggestions or bottlenecks caused by incorrect key assignment. Using key={} ensures a smooth and efficient rendering process for dynamic lists.

By following these PSR requirements, UI code development maintains a high level of performance, scalability, and reliability. This results in a responsive and robust application that delivers exceptional user experience, even as the application grows and evolves.

Security and Patching Guidelines

In UI code development, ensuring robust security measures is imperative. Here are guidelines that should be diligently followed to safeguard against potential vulnerabilities and enhance overall security:

  • XSS Attack Prevention

    All input fields within your application must undergo thorough validation to guard against Cross-Site Scripting (XSS) attacks. These attacks occur when malicious code is injected into an application via user-provided input, potentially compromising the integrity and security of your application.

  • Role-Based Access Control (RBAC)

    To maintain strict control over user actions, implement Role-Based Access Control (RBAC) within your application. This mechanism segregates users into roles, typically 'platform' and 'product', allowing you to determine who can perform specific actions.

  • RBAC Interface Implementation

    • For effective RBAC implementation, use Higher-Order Components (HOCs) to determine the visibility of components based on the user's role.

    • To check if a component satisfies the 'platform' role, utilize the 'HasPlatformPermission' HOC. This HOC provides flexibility in setting additional conditions and handling errors.

    • Similarly, for the 'product' role, employ the 'HasProjectPermission' HOC to ensure the visibility of components according to role-specific criteria.

  • RBAC Hooks:

    • Leverage the provided RBAC hooks for more granular control:

      • 'usePlatformPermission' hook exposes 'useHasPlatformPermission' method.

      • 'useProjectPermission' hook exposes 'useHasProjectPermission' method. These hooks simplify the implementation of RBAC business logic within your components.

  • Cross-Site Scripting (XSS) Protection with DOMPurify

    • When dealing with user-generated content, such as comments or messages that may contain HTML, it's essential to sanitize and clean the input. Utilize libraries like DOMPurify to effectively prevent XSS attacks.

    • DOMPurify is designed to strip or neutralize potentially dangerous HTML and scripting code while preserving safe content. In your code, you can integrate DOMPurify as demonstrated:

    Copy
    import DOMPurify from 'dompurify';
    const userGeneratedHTML = '<p>This is <script>alert("XSS");</script> a test.</p>';
    const sanitizedHTML = DOMPurify.sanitize(userGeneratedHTML);
    const contentDiv = document.getElementById('content');
    contentDiv.innerHTML = sanitizedHTML;
  • Integration with Snyk

    • Ensure seamless integration with Snyk, a tool that offers continuous monitoring of your project's dependencies, code security, and code quality.

    • Snyk identifies vulnerabilities as part of your development pipeline, allowing for prompt addressing of issues.

    • This tool not only detects vulnerabilities but also provides recommendations and guidance on how to update dependencies to secure versions, enhancing your application's resilience against potential threats.

By adhering to these comprehensive security and patching guidelines, you fortify your UI code against vulnerabilities, adhere to stringent access control, and continuously monitor for potential security risks, ultimately ensuring a safer and more reliable user experience.

Documentation Guidelines for UI Code Development

In the field of UI code development, comprehensive documentation is a required for better maintainability, collaboration, and future-proofing your projects. Here are key guidelines to follow:

Code Comments for Maintainability

  • Developers are required to incorporate meaningful and well-structured code comments throughout their codebase. These comments help others understand the code's logic, intent, and functionality.

  • Code comments should be clear, concise, and follow a consistent format to enhance readability and maintainability. Each major function, class, or complex logic block should have corresponding comments that explain its purpose and how it works.

  • Comments should not merely reiterate the code but should provide insights into the 'why' behind the code. Developers should explain the reasoning, algorithms used, and any significant considerations.

  • Properly commented code is not just for the benefit of individual developers but also for the entire development team, making it easier to collaborate, troubleshoot, and extend the codebase.

README.md Files for Widgets

For any widgets, components, or significant modules developed, it's mandatory to maintain a well-structured README.md file. This file should reside within the widget's directory or repository.

The README.md file serves as the entry point and documentation hub for understanding the widget's purpose, usage, configuration, and integration.

README.md files are not only crucial for internal development but also beneficial when sharing widgets with the broader community. A well-documented widget encourages adoption and collaboration among developers.

Related Topics Link IconRecommended Topics

What's next? Coding Standards for Backend Code Development